import Callout from 'components/Callout';

# Next.js middleware for i18n routing

The middleware handles redirects and rewrites based on the detected user locale.

```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  // A list of all locales that are supported
  locales: ['en', 'de'],

  // Used when no locale matches
  defaultLocale: 'en'
});

export const config = {
  // Match only internationalized pathnames
  matcher: ['/', '/(de|en)/:path*']
};
```

In addition to handling i18n routing, the middleware sets [the `link` header](https://developers.google.com/search/docs/specialty/international/localized-versions#http) to inform search engines that your content is available in different languages.

## Strategies

There are two strategies for detecting the locale:

1.  [Prefix-based routing (default)](#prefix-based-routing)
2.  [Domain-based routing](#domain-based-routing)

Once a locale is detected, it will be saved in a cookie.

### Strategy 1: Prefix-based routing (default) [#prefix-based-routing]

Since your pages are nested within a `[locale]` folder, all routes are by default prefixed with one of your supported locales (e.g. `/en/about`).

#### Locale detection

The locale is detected based on these priorities:

1. A locale prefix is present in the pathname (e.g. `/en/about`)
2. A cookie is present that contains a previously detected locale
3. A locale can be matched based on the [`accept-language` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language)
4. As a last resort, the `defaultLocale` is used

To change the locale, users can visit a prefixed route. This will take precedence over a previously matched locale that is saved in a cookie or the `accept-language` header and will update the previous cookie value.

**Example workflow:**

1. A user requests `/` and based on the `accept-language` header, the `en` locale is matched.
2. The `en` locale is saved in a cookie and the user is redirected to `/en`.
3. The app renders `<Link locale="de" href="/">Switch to German</Link>` to allow the user to change the locale to `de`.
4. When the user clicks on the link, a request to `/de` is initiated.
5. The middleware will update the cookie value to `de`.

<Callout>
  You can optionally remove the locale prefix in pathnames by changing the
  [`localePrefix`](#locale-prefix) setting.
</Callout>

### Strategy 2: Domain-based routing [#domain-based-routing]

If you want to serve your localized content based on different domains, you can provide a list of mappings between domains and locales to the middleware.

**Examples:**

- `us.example.com/en`
- `ca.example.com/en`
- `ca.example.com/fr`

```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  // All locales across all domains
  locales: ['en', 'fr'],

  // Used when no domain matches (e.g. on localhost)
  defaultLocale: 'en',

  domains: [
    {
      domain: 'us.example.com',
      defaultLocale: 'en',
      // Optionally restrict the locales managed by this domain. If this
      // domain receives requests for another locale (e.g. us.example.com/fr),
      // then the middleware will redirect to a domain that supports it.
      locales: ['en']
    },
    {
      domain: 'ca.example.com',
      defaultLocale: 'en'
      // If there are no `locales` specified on a domain,
      // all global locales will be supported here.
    }
  ]
});
```

<Callout>
  You can optionally remove the locale prefix in pathnames by changing the
  [`localePrefix`](#locale-prefix) setting.
</Callout>

#### Locale detection

To match the request against the available domains, the host is read from the `x-forwarded-host` header, with a fallback to `host`.

The locale is detected based on these priorities:

1. A locale prefix is present in the pathname and the domain supports it (e.g. `ca.example.com/fr`)
2. If the host of the request is configured in `domains`, the `defaultLocale` of the domain is used
3. As a fallback, the [locale detection of prefix-based routing](#locale-detection) applies

Since the middleware is aware of all your domains, the domain will automatically be switched when the user requests to change the locale.

**Example workflow:**

1. The user requests `us.example.com` and based on the `defaultLocale` of this domain, the `en` locale is matched.
2. The app renders `<Link locale="fr" href="/">Switch to French</Link>` to allow the user to change the locale to `fr`.
3. When the link is clicked, a request to `us.example.com/fr` is initiated.
4. The middleware recognizes that the user wants to switch to another domain and responds with a redirect to `ca.example.com/fr`.

<details>
<summary>How is the best matching domain for a given locale detected?</summary>

The bestmatching domain is detected based on these priorities:

1. Stay on the current domain if the locale is supported here
2. Use an alternative domain where the locale is configured as the `defaultLocale`
3. Use an alternative domain where the available `locales` are restricted and the locale is supported
4. Stay on the current domain if it supports all locales
5. Use an alternative domain that supports all locales

</details>

## Further configuration

### Locale prefix

If you're using [the navigation APIs from `next-intl`](/docs/routing/navigation), you want to make sure your `localePrefix` setting matches your middleware configuration.

#### Always use a locale prefix [#locale-prefix-always]

By default, pathnames always start with the locale (e.g. `/en/about`).

```tsx filename="middleware.ts" {6}
import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  // ... other config

  localePrefix: 'always' // This is the default
});
```

<details>
<summary>How can I redirect unprefixed pathnames?</summary>

If you want to redirect unprefixed pathnames like `/about` to a prefixed alternative like `/en/about`, you can adjust your middleware matcher to [match unprefixed pathnames](#matcher-no-prefix) too.

</details>

#### Don't use a locale prefix for the default locale [#locale-prefix-as-needed]

If you don't want to include a locale prefix for the default locale, but only for non-default locales, you can configure the middleware accordingly.

```tsx filename="middleware.ts" {6}
import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  // ... other config

  localePrefix: 'as-needed'
});
```

In this case, requests where the locale prefix matches the default locale will be redirected (e.g. `/en/about` to `/about`). This will affect both prefix-based as well as domain-based routing.

**Important:** If you use this strategy, you should make sure that [your matcher detects unprefixed pathnames](#matcher-no-prefix).

<Callout>
  If you use [the `Link` component](/docs/routing/navigation#link), the initial
  render will point to the prefixed version but will be patched immediately on
  the client once the component detects that the default locale has rendered.
  The prefixed version is still valid, but SEO tools might report a hint that
  the link points to a redirect.
</Callout>

#### Never use a locale prefix [#locale-prefix-never]

If you're building an app behind an authentication layer, where there is no need for SEO, or you're using [domain-based routing](#domain-based-routing) and you support only a single locale per domain, you can configure the middleware to never show a locale prefix in the URL.

```tsx filename="middleware.ts" {6}
import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  // ... other config

  localePrefix: 'never'
});
```

In this case, requests for all locales will be rewritten to have the locale only prefixed internally. You still need to place all your pages inside a `[locale]` folder for the routes to be able to receive the `locale` param.

**Important:** If you use this strategy, you should make sure that [your matcher detects unprefixed pathnames](#matcher-no-prefix).

<Callout>
  Note that [alternate links](#disable-alternate-links) are disabled in this
  mode since there might not be distinct URLs per locale.
</Callout>

### Disable automatic locale detection

If you want to rely entirely on the URL to resolve the locale, you can disable locale detection based on the `accept-language` header and a potentially existing cookie value from a previous visit.

```tsx filename="middleware.ts" {6}
import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  // ... other config

  localeDetection: false
});
```

In this case, only the locale prefix and a potentially [matching domain](#domain-based-routing) are used to determine the locale.

### Disable alternate links

The middleware automatically sets [the `link` header](https://developers.google.com/search/docs/specialty/international/localized-versions#http) to inform search engines that your content is available in different languages. Note that this automatically integrates with your routing strategy and will generate the correct links based on your configuration.

If you prefer to include these links yourself, you can opt-out of this behavior.

```tsx filename="middleware.ts" {6}
import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  // ... other config

  alternateLinks: false // Defaults to `true`
});
```

<details>
<summary>Can I customize the alternate links?</summary>

The alternate links can either be turned off or on, depending on the `alternateLinks` option.

If you need to customize the alternate links, you can either turn them off and provide your own implementation, or if you only need to make minor adaptions, you can [compose the middleware](#composing-other-middlewares) and add your custom logic after the middleware has run:

```tsx filename="middleware.ts"
import createIntlMiddleware from 'next-intl/middleware';
import LinkHeader from 'http-link-header';
import {NextRequest} from 'next/server';

const handleI18nRouting = createIntlMiddleware(/* ... */);

export default async function middleware(request: NextRequest) {
  const response = handleI18nRouting(request);

  // Example: Remove the `x-default` entry
  const link = LinkHeader.parse(response.headers.get('link'));
  link.refs = link.refs.filter((entry) => entry.hreflang !== 'x-default');
  response.headers.set('link', link.toString());

  return response;
}
```

</details>

### Localizing pathnames

Many apps choose to localize pathnames, especially when search engine optimization is relevant, e.g.:

- `/en/about`
- `/de/ueber-uns`

Since you want to define these routes only once internally, you can use the `next-intl` middleware to [rewrite](https://nextjs.org/docs/api-reference/next.config.js/rewrites) such incoming requests to shared pathnames.

```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  defaultLocale: 'en',
  locales: ['en', 'de'],

  // The `pathnames` object holds pairs of internal and
  // external paths. Based on the locale, the external
  // paths are rewritten to the shared, internal ones.
  pathnames: {
    // If all locales use the same pathname, a single
    // external path can be used for all locales.
    '/': '/',
    '/blog': '/blog',

    // If locales use different paths, you can
    // specify each external path per locale.
    '/about': {
      en: '/about',
      de: '/ueber-uns'
    },

    // Dynamic params are supported via square brackets
    '/news/[articleSlug]-[articleId]': {
      en: '/news/[articleSlug]-[articleId]',
      de: '/neuigkeiten/[articleSlug]-[articleId]'
    },

    // Also (optional) catch-all segments are supported
    '/categories/[...slug]': {
      en: '/categories/[...slug]',
      de: '/kategorien/[...slug]'
    }
  }
});
```

<Callout>
  If you have pathname localization set up in the middleware, you likely want to
  use the [localized navigation
  APIs](/docs/routing/navigation#localized-pathnames) in your components.
</Callout>

### Matcher config

The middleware is intended to only run on pages, not on arbitrary files that you serve independently of the user locale (e.g. `/favicon.ico`).

Because of this, the following config is generally recommended:

```tsx filename="middleware.ts"
export const config = {
  // Match only internationalized pathnames
  matcher: ['/', '/(de|en)/:path*']
};
```

This enables:

1. A redirect at `/` to a suitable locale
2. Internationalization of all pathnames starting with a locale (e.g. `/en/about`)

#### Pathnames without a locale prefix [#matcher-no-prefix]

There are two use cases where you might want to match pathnames without a locale prefix:

1. You're using a config for [`localePrefix`](#locale-prefix) other than [`always`](#locale-prefix-always)
2. You want to implement redirects that add a locale for unprefixed pathnames (e.g. `/about` → `/en/about`)

For these cases, the middleware should run on requests for pathnames without a locale prefix as well.

A popular strategy is to match all routes that don't start with certain segments (e.g. `/_next`) and also none that include a dot (`.`) since these typically indicate static files. However, if you have some routes where a dot is expected (e.g. `/users/jane.doe`), you should explicitly provide a matcher for these.

```tsx filename="middleware.ts"
export const config = {
  // Matcher entries are linked with a logical "or", therefore
  // if one of them matches, the middleware will be invoked.
  matcher: [
    // Match all pathnames except for
    // - … if they start with `/api`, `/_next` or `/_vercel`
    // - … the ones containing a dot (e.g. `favicon.ico`)
    '/((?!api|_next|_vercel|.*\\..*).*)',
    // However, match all pathnames within `/users`, optionally with a locale prefix
    '/([\\w-]+)?/users/(.+)'
  ]
};
```

{/* Keep this in sync with `packages/next-intl/test/middleware/middleware.test.tsx` */}

Note that some third-party providers like [Vercel Analytics](https://vercel.com/analytics) and [umami](https://umami.is/docs/running-on-vercel) typically use internal endpoints that are then rewritten to an external URL (e.g. `/_vercel/insights/view`). Make sure to exclude such requests from your middleware matcher so they aren't accidentally rewritten.

### Base path

The `next-intl` middleware as well as [the navigation APIs](/docs/routing/navigation) will automatically pick up a [`basePath`](https://nextjs.org/docs/app/api-reference/next-config-js/basePath) that you might have configured in your `next.config.js`.

Note however that you should make sure that your [middleware `matcher`](#matcher-config) matches the root of your base path:

```tsx filename="middleware.ts"
// ...

export const config = {
  matcher: [
    '/' // Make sure the root of your base path is matched

    // ... other matcher config
  ]
};
```

See also: [`vercel/next.js#47085`](https://github.com/vercel/next.js/issues/47085)

## Composing other middlewares

By calling `createMiddleware`, you'll receive a function of the following type:

```tsx
middleware(request: NextRequest): NextResponse
```

If you need to incorporate additional behavior, you can either modify the request before the `next-intl` middleware receives it, or modify the response that is returned.

```tsx filename="middleware.ts"
import createIntlMiddleware from 'next-intl/middleware';
import {NextRequest} from 'next/server';

export default async function middleware(request: NextRequest) {
  // Step 1: Use the incoming request (example)
  const defaultLocale = request.headers.get('x-your-custom-locale') || 'en';

  // Step 2: Create and call the next-intl middleware (example)
  const handleI18nRouting = createIntlMiddleware({
    locales: ['en', 'de'],
    defaultLocale
  });
  const response = handleI18nRouting(request);

  // Step 3: Alter the response (example)
  response.headers.set('x-your-custom-locale', defaultLocale);

  return response;
}

export const config = {
  // Match only internationalized pathnames
  matcher: ['/', '/(de|en)/:path*']
};
```

### Example: Additional rewrites

If you need to handle rewrites apart from the ones provided by `next-intl`, you can adjust the `pathname` of the `request` before invoking the `next-intl` middleware (based on ["A/B Testing with Cookies" by Vercel](https://vercel.com/templates/next.js/cookies)).

This example rewrites requests for `/[locale]/profile` to `/[locale]/profile/new` if a special cookie is set.

```tsx filename="middleware.ts"
import createIntlMiddleware from 'next-intl/middleware';
import {NextRequest} from 'next/server';

export default async function middleware(request: NextRequest) {
  const [, locale, ...segments] = request.nextUrl.pathname.split('/');

  if (locale != null && segments.join('/') === 'profile') {
    const usesNewProfile =
      (request.cookies.get('NEW_PROFILE')?.value || 'false') === 'true';

    if (usesNewProfile) {
      request.nextUrl.pathname = `/${locale}/profile/new`;
    }
  }

  const handleI18nRouting = createIntlMiddleware({
    locales: ['en', 'de'],
    defaultLocale: 'en'
  });
  const response = handleI18nRouting(request);
  return response;
}

export const config = {
  matcher: ['/', '/(de|en)/:path*']
};
```

Note that if you use a [`localePrefix`](#locale-prefix) other than `always`, you need to adapt the handling appropriately to handle unprefixed pathnames too.

### Example: Integrating with Clerk

[`@clerk/nextjs`](https://clerk.com/docs/references/nextjs/overview) provides a middleware that can be integrated with `next-intl` by using the [`beforeAuth` hook](https://clerk.com/docs/references/nextjs/auth-middleware#using-before-auth-to-execute-middleware-before-authentication). By doing this, the middleware from `next-intl` will run first, potentially redirect or rewrite incoming requests, followed by the middleware from `@clerk/next` acting on the response.

```tsx filename="middleware.ts"
import {authMiddleware} from '@clerk/nextjs';
import createMiddleware from 'next-intl/middleware';

const intlMiddleware = createMiddleware({
  locales: ['en', 'de'],
  defaultLocale: 'en'
});

export default authMiddleware({
  beforeAuth(request) {
    return intlMiddleware(request);
  },

  // Ensure that locale-specific sign in pages are public
  publicRoutes: ['/:locale', '/:locale/sign-in']
});

export const config = {
  // Match only internationalized pathnames
  matcher: ['/', '/(de|en)/:path*']
};
```

### Example: Integrating with Supabase Authentication

[`@supabase/auth-helpers-nextjs`](https://supabase.com/docs/guides/auth/auth-helpers/nextjs?language=ts#managing-session-with-middleware) exports `createMiddlewareClient` that accepts a `req` and `res` object. We can pass the incoming `req` and the `res` created with `createIntlMiddleware` to it.

```tsx filename="middleware.ts"
import {createMiddlewareClient} from '@supabase/auth-helpers-nextjs';
import createIntlMiddleware from 'next-intl/middleware';
import type {NextRequest} from 'next/server';
import type {Database} from '@/lib/database.types';

export default async function middleware(req: NextRequest) {
  const handleI18nRouting = createIntlMiddleware({
    locales: ['en', 'de'],
    defaultLocale: 'en'
  });
  const res = handleI18nRouting(req);

  const supabase = createMiddlewareClient<Database>({req, res});
  await supabase.auth.getSession();

  return res;
}

export const config = {
  matcher: ['/', '/(de|en)/:path*']
};
```

### Example: Integrating with Auth.js (aka NextAuth.js) [#example-auth-js]

The Next.js middleware of [Auth.js](https://authjs.dev/) requires an integration with their control flow to be compatible with other middlewares. The [success callback](https://next-auth.js.org/configuration/nextjs#wrap-middleware) can be used to run the `next-intl` middleware on authorized pages. However, public pages need to be treated separately.

For pathnames specified in [the `pages` object](https://next-auth.js.org/configuration/nextjs#pages) (e.g. `signIn`), Auth.js will skip the entire middleware and not run the success callback. Therefore, we have to detect these pages before running the Auth.js middleware and only run the `next-intl` middleware in this case.

```tsx filename="middleware.ts"
import {withAuth} from 'next-auth/middleware';
import createIntlMiddleware from 'next-intl/middleware';
import {NextRequest} from 'next/server';

const locales = ['en', 'de'];
const publicPages = ['/', '/login'];

const intlMiddleware = createIntlMiddleware({
  locales,
  localePrefix: 'as-needed',
  defaultLocale: 'en'
});

const authMiddleware = withAuth(
  // Note that this callback is only invoked if
  // the `authorized` callback has returned `true`
  // and not for pages listed in `pages`.
  function onSuccess(req) {
    return intlMiddleware(req);
  },
  {
    callbacks: {
      authorized: ({token}) => token != null
    },
    pages: {
      signIn: '/login'
    }
  }
);

export default function middleware(req: NextRequest) {
  const publicPathnameRegex = RegExp(
    `^(/(${locales.join('|')}))?(${publicPages
      .flatMap((p) => (p === '/' ? ['', '/'] : p))
      .join('|')})/?$`,
    'i'
  );
  const isPublicPage = publicPathnameRegex.test(req.nextUrl.pathname);

  if (isPublicPage) {
    return intlMiddleware(req);
  } else {
    return (authMiddleware as any)(req);
  }
}

export const config = {
  matcher: ['/((?!api|_next|.*\\..*).*)']
};
```

<Callout>

There's a working [example that combines `next-intl` with Auth.js](/examples#app-router-next-auth) on GitHub.

</Callout>

## Usage without middleware (static export)

If you're using the [static export feature from Next.js](https://nextjs.org/docs/app/building-your-application/deploying/static-exports) (`output: 'export'`), the middleware will not run. You can use [prefix-based routing](#prefix-based-routing) nontheless to internationalize your app, but a few tradeoffs apply.

**Static export limitations:**

1. There's no default locale that can be used without a prefix (same as [`localePrefix: 'always'`](#locale-prefix-always))
2. The locale can't be negotiated at runtime (same as [`localeDetection: false`](#disable-automatic-locale-detection))
3. You can't use [pathname localization](#localizing-pathnames)
4. This requires [static rendering](/docs/getting-started/app-router#static-rendering)
5. You need to add a redirect for the root of the app

```tsx filename="app/page.tsx"
import {redirect} from 'next/navigation';

// Redirect the user to the default locale when `/` is requested
export default function RootPage() {
  redirect('/en');
}
```

You can explore a working demo by building [the Next.js App Router example](/examples#app-router) after enabling the static export:

```tsx filename="next.config.js"
module.exports = {
  output: 'export'
};
```

## Troubleshooting

### "Unable to find `next-intl` locale because the middleware didn't run on this request." [#unable-to-find-locale]

This can happen either because:

1. The middleware is not set up.
2. The middleware is set up in the wrong file (e.g. you're using the `src` folder, but `middleware.ts` was added in the root folder).
3. The middleware matcher didn't match a request, but you're using APIs from `next-intl` in a component.
4. You're attempting to implement static rendering via [`force-static`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic).

To recover from this error, please make sure that:

1. You've [set up the middleware](/docs/getting-started/app-router#middlewarets).
2. You're using APIs from `next-intl` (including [the navigation APIs](/docs/routing/navigation)) exclusively within the `locale` segment.
3. Your [middleware matcher](#matcher-config) matches all routes of your application, including dynamic segments with potentially unexpected characters like dots (e.g. `/users/jane.doe`).
4. If you're using [`localePrefix: 'as-needed'`](#locale-prefix-as-needed), the `locale` segment effectively acts like a catch-all for all unknown routes. You should make sure that `params.locale` is [validated](/docs/usage/configuration#i18nts) before it's used by any APIs from `next-intl`.
5. To implement static rendering, make sure to [provide a static locale](/docs/getting-started/app-router#static-rendering) to `next-intl` instead of using `force-static`.

Note that `next-intl` will print this warning only during development and will invoke the `notFound()` function to abort the render. You should consider adding [a `not-found` page](/docs/environments/error-files#not-foundjs) due to this.
